<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Dense Optical Flow Example</title>
<link href="js_example_style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h2>Dense Optical Flow Example</h2>
<p>
    Click <b>Start/Stop</b> button to start or stop the video.<br>
    The <b>videoInput</b> is a &lt;video&gt; element used as input.
    The <b>canvasOutput</b> is a &lt;canvas&gt; element used as output.<br>
    We get a 2-channel array with optical flow vectors, (u,v). We find their magnitude and direction.
    We color code the result for better visualization. Direction corresponds to Hue value of the image.
    Magnitude corresponds to Value plane.<br>
    The code of &lt;textarea&gt; will be executed when video is started.<br>
    You can modify the code to investigate more.
</p>
<div>
<div class="control"><button id="startAndStop" disabled>Start</button></div>
<textarea class="code" rows="29" cols="100" id="codeEditor" spellcheck="false">
</textarea>
</div>
<p class="err" id="errorMessage"></p>
<div>
    <table cellpadding="0" cellspacing="0" width="0" border="0">
    <tr>
        <td>
            <video id="videoInput" width="320" height="240" muted></video>
        </td>
        <td>
            <canvas id="canvasOutput" width="320" height="240" ></canvas>
        </td>
        <td></td>
        <td></td>
    </tr>
    <tr>
        <td>
            <div class="caption">videoInput</div>
        </td>
        <td>
            <div class="caption">canvasOutput</div>
        </td>
        <td></td>
        <td></td>
    </tr>
    </table>
</div>
<script src="https://webrtc.github.io/adapter/adapter-5.0.4.js" type="text/javascript"></script>
<script src="utils.js" type="text/javascript"></script>
<script id="codeSnippet" type="text/code-snippet">
let video = document.getElementById('videoInput');
let cap = new cv.VideoCapture(video);

// take first frame of the video
let frame1 = new cv.Mat(video.height, video.width, cv.CV_8UC4);
cap.read(frame1);

let prvs = new cv.Mat();
cv.cvtColor(frame1, prvs, cv.COLOR_RGBA2GRAY);
frame1.delete();
let hsv = new cv.Mat();
let hsv0 = new cv.Mat(video.height, video.width, cv.CV_8UC1);
let hsv1 = new cv.Mat(video.height, video.width, cv.CV_8UC1, new cv.Scalar(255));
let hsv2 = new cv.Mat(video.height, video.width, cv.CV_8UC1);
let hsvVec = new cv.MatVector();
hsvVec.push_back(hsv0); hsvVec.push_back(hsv1); hsvVec.push_back(hsv2);

let frame2 = new cv.Mat(video.height, video.width, cv.CV_8UC4);
let next = new cv.Mat(video.height, video.width, cv.CV_8UC1);
let flow = new cv.Mat(video.height, video.width, cv.CV_32FC2);
let flowVec = new cv.MatVector();
let mag = new cv.Mat(video.height, video.width, cv.CV_32FC1);
let ang = new cv.Mat(video.height, video.width, cv.CV_32FC1);
let rgb = new cv.Mat(video.height, video.width, cv.CV_8UC3);

const FPS = 30;
function processVideo() {
    try {
        if (!streaming) {
            // clean and stop.
            prvs.delete(); hsv.delete(); hsv0.delete(); hsv1.delete(); hsv2.delete();
            hsvVec.delete(); frame2.delete(); flow.delete(); flowVec.delete(); next.delete();
            mag.delete(); ang.delete(); rgb.delete();
            return;
        }
        let begin = Date.now();

        // start processing.
        cap.read(frame2);
        cv.cvtColor(frame2, next, cv.COLOR_RGBA2GRAY);
        cv.calcOpticalFlowFarneback(prvs, next, flow, 0.5, 3, 15, 3, 5, 1.2, 0);
        cv.split(flow, flowVec);
        let u = flowVec.get(0);
        let v = flowVec.get(1);
        cv.cartToPolar(u, v, mag, ang);
        u.delete(); v.delete();
        ang.convertTo(hsv0, cv.CV_8UC1, 180/Math.PI/2);
        cv.normalize(mag, hsv2, 0, 255, cv.NORM_MINMAX, cv.CV_8UC1);
        cv.merge(hsvVec, hsv);
        cv.cvtColor(hsv, rgb, cv.COLOR_HSV2RGB);
        cv.imshow('canvasOutput', rgb);
        next.copyTo(prvs);

        // schedule the next one.
        let delay = 1000/FPS - (Date.now() - begin);
        setTimeout(processVideo, delay);
    } catch (err) {
        utils.printError(err);
    }
};

// schedule the first one.
setTimeout(processVideo, 0);
</script>
<script type="text/javascript">
let utils = new Utils('errorMessage');

utils.loadCode('codeSnippet', 'codeEditor');

let streaming = false;
let videoInput = document.getElementById('videoInput');
let startAndStop = document.getElementById('startAndStop');

startAndStop.addEventListener('click', () => {
    if (!streaming) {
        utils.clearError();
        videoInput.play().then(() => {
            onVideoStarted();
        });
    } else {
        videoInput.pause();
        videoInput.currentTime = 0;
        onVideoStopped();
    }
});

function onVideoStarted() {
    streaming = true;
    startAndStop.innerText = 'Stop';
    videoInput.height = videoInput.width * (videoInput.videoHeight / videoInput.videoWidth);
    utils.executeCode('codeEditor');
}

function onVideoStopped() {
    streaming = false;
    startAndStop.innerText = 'Start';
}

videoInput.addEventListener('ended', () => {
    onVideoStopped();
});

utils.loadOpenCv(() => {
    videoInput.addEventListener('canplay', () => {
        startAndStop.removeAttribute('disabled');
    });
    videoInput.src = 'box.mp4';
});
</script>
</body>
</html>
